// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: xguo

#pragma once

#include <cmath>
#include <ctgmath>
#include <memory>
#include <string>

#include <fmt/format.h>

#include "coin2/exchange/base/order/external.h"

namespace coin2::exchange::base::order {

struct OrderSpec {
  using IProduct = coin2::exchange::base::symbology::IProduct;
  using ExchangeType = coin2::exchange::base::symbology::ExchangeType;
  using MarketType = coin2::exchange::base::symbology::MarketType;
  using OrderDirection = coin::proto::OrderDirection;
  using OrderType = coin::proto::OrderType;
  using OrderSide = coin::proto::OrderSide;
  using OrderDuration = coin::proto::OrderDuration;

  OrderSpec(
      const IProduct& product,
      const OrderType order_type,
      const OrderSide order_side,
      const OrderDuration duration,
      const double price,
      const double qty,
      const int64_t order_created_time,
      const bool post_only,
      const double leverage_rate,
      const std::string& tag)
      : OrderSpec(
            product.order_native_symbol(),
            product.absolute_norm(),
            product.exchange(),
            product.market(),
            order_type,
            order_side,
            duration,
            price,
            qty,
            order_created_time,
            post_only,
            false,
            leverage_rate,
            tag) {}

  OrderSpec(
      const std::string product,
      const std::string norm_product,
      const ExchangeType exchange,
      const MarketType market_type,
      const OrderType order_type,
      const OrderSide order_side,
      const OrderDuration duration,
      const double price,
      const double qty,
      const int64_t order_created_time,
      const bool post_only,
      const bool reduce_only,
      const double leverage_rate,
      const std::string& tag)
      : product(product),
        norm_product(norm_product),
        exchange(exchange),
        market_type(market_type),
        order_type(order_type),
        order_side(order_side),
        duration(duration),
        price(price),
        qty(qty),
        order_created_time(order_created_time),
        post_only(post_only),
        reduce_only(reduce_only),
        leverage_rate(leverage_rate),
        tag(tag) {
    CHECK_GT(order_created_time, 10000);
  }

  OrderSpec CloneAsAmendOrder(int64_t proc_order_id, double amend_price, double amend_qty) const {
    auto new_price = price;
    auto is_price_amended_tmp = false;
    if (!isnan(amend_price) && std::abs(amend_price - price) > 1e-9) {
      new_price = amend_price;
      is_price_amended_tmp = true;
    }
    auto new_qty = qty;
    auto is_qty_amended_tmp = false;
    if (!isnan(amend_qty) && std::abs(amend_qty - qty) > 1e-9) {
      new_qty = amend_qty;
      is_qty_amended_tmp = true;
    }
    OrderSpec order_spec{
        product,
        norm_product,
        exchange,
        market_type,
        order_type,
        order_side,
        duration,
        new_price,
        new_qty,
        GetCurrentTimestamp(),
        post_only,
        reduce_only,
        leverage_rate,
        tag};
    order_spec.SetOrigProcOrderId(proc_order_id);
    order_spec.SetAmend(true);
    order_spec.SetIsPriceAmended(is_price_amended_tmp);
    order_spec.SetIsQtyAmended(is_qty_amended_tmp);
    return order_spec;
  }

  // map {BUY_OPEN, BUY_CLOSE, ...} => {BUY, SELL}
  double sign() const {
    switch (order_side) {
      case OrderSide::BUY_ORDER:
      case OrderSide::BUY_OPEN_ORDER:
      case OrderSide::BUY_CLOSE_ORDER:
        // case OrderSide::BUY_CLOSE_CARRYOVER_ORDER:
        return 1;
      case OrderSide::SELL_ORDER:
      case OrderSide::SELL_SHORT_ORDER:
      case OrderSide::SELL_OPEN_ORDER:
      case OrderSide::SELL_CLOSE_ORDER:
        // case OrderSide::SELL_CLOSE_CARRYOVER_ORDER:
        return -1;
      default:
        NOTREACHED() << OrderSide_Name(order_side);
        return 0;
    }
    return 0.0;
  }
  // return 1 or -1
  OrderDirection direction() const {
    switch (order_side) {
      case OrderSide::BUY_ORDER:
      case OrderSide::BUY_OPEN_ORDER:
      case OrderSide::BUY_CLOSE_ORDER:
        // case OrderSide::BUY_CLOSE_CARRYOVER_ORDER:
        return OrderDirection::DIRECTION_BUY;
      case OrderSide::SELL_ORDER:
      case OrderSide::SELL_SHORT_ORDER:
      case OrderSide::SELL_OPEN_ORDER:
      case OrderSide::SELL_CLOSE_ORDER:
        // case OrderSide::SELL_CLOSE_CARRYOVER_ORDER:
        return OrderDirection::DIRECTION_SELL;
      default:
        NOTREACHED() << OrderSide_Name(order_side);
        return OrderDirection::DIRECTION_UNINITIALIZED;
    }
  }

  void ResetDuration() const { duration = OrderDuration::GTC_ORDER; }
  void ResetPostOnly() const { post_only = false; }
  void SetAmend(bool use_amend_in) { use_amend = use_amend_in; }
  void SetOrigProcOrderId(int64_t orig_proc_order_id_in) {
    orig_proc_order_id = orig_proc_order_id_in;
  }
  void SetIsPriceAmended(bool amended) { is_price_amended = amended; }
  void SetIsQtyAmended(bool amended) { is_qty_amended = amended; }
  void SetReduceOnly(bool reduce_only_in) { reduce_only = reduce_only_in; }

  std::string DebugString() const {
    return fmt::format(
        "OrderSpec( {}.{}.{}, final_price={}, final_qty={}, order_type={}, order_side={}, "
        "order_duration={} )",
        MarketType_Name(market_type),
        ExchangeType_Name(exchange),
        product,
        price,
        qty,
        OrderType_Name(order_type),
        OrderSide_Name(order_side),
        OrderDuration_Name(duration));
  }

  OrderSpecProto ToProto() const {
    OrderSpecProto proto;
    proto.set_product(product);
    proto.set_norm_product(norm_product);
    proto.set_exchange_type(exchange);
    proto.set_market_type(market_type);
    proto.set_order_type(order_type);
    proto.set_order_side(order_side);
    proto.set_duration(duration);
    proto.set_price(price);
    proto.set_qty(qty);
    proto.set_order_created_time(order_created_time);
    proto.set_post_only(post_only);
    proto.set_reduce_only(reduce_only);
    proto.set_leverage_rate(leverage_rate);
    proto.set_tag(tag);
    proto.set_use_amend(use_amend);
    proto.set_orig_proc_order_id(orig_proc_order_id);
    proto.set_is_price_amended(is_price_amended);
    proto.set_is_qty_amended(is_qty_amended);
    proto.set_proc_order_id(proc_order_id);
    return proto;
  }

  static std::unique_ptr<OrderSpec> FromProto(const OrderSpecProto& proto_msg) {
    auto order_spec = std::make_unique<OrderSpec>(
        proto_msg.product(),
        proto_msg.norm_product(),
        proto_msg.exchange_type(),
        proto_msg.market_type(),
        proto_msg.order_type(),
        proto_msg.order_side(),
        proto_msg.duration(),
        proto_msg.price(),
        proto_msg.qty(),
        proto_msg.order_created_time(),
        proto_msg.post_only(),
        proto_msg.reduce_only(),
        proto_msg.leverage_rate(),
        proto_msg.tag());
    order_spec->use_amend = proto_msg.use_amend();
    order_spec->orig_proc_order_id = proto_msg.orig_proc_order_id();
    order_spec->is_price_amended = proto_msg.is_price_amended();
    order_spec->is_qty_amended = proto_msg.is_qty_amended();
    order_spec->proc_order_id = proto_msg.proc_order_id();
    return order_spec;
  }

  const std::string product;
  const std::string norm_product;
  const ExchangeType exchange;
  const MarketType market_type;
  const OrderType order_type;
  const OrderSide order_side;
  mutable OrderDuration duration;
  const double price;
  const double qty;
  const int64_t order_created_time;
  mutable bool post_only;
  bool reduce_only;
  const double leverage_rate;
  const std::string tag;

  bool use_amend{false};
  int64_t orig_proc_order_id{0};
  bool is_price_amended{false};
  bool is_qty_amended{false};
  mutable int64_t proc_order_id{0};
  mutable int64_t order_expiration_ts{0};
  mutable std::string mutable_tag;
};

struct BatchOrdersSpec {
  void AddOrderSpec(const OrderSpec& order_spec) { order_spec_deque.emplace_back(order_spec); }
  void AddOrderSpec(OrderSpec&& order_spec) {
    order_spec_deque.emplace_back(std::move(order_spec));
  }
  auto GetOrderSpecs() const -> const std::deque<OrderSpec>& { return order_spec_deque; }
  bool Empty() const { return 0 == order_spec_deque.size(); }
  void Clear() { order_spec_deque.clear(); }

 private:
  std::deque<OrderSpec> order_spec_deque;
};

struct BatchCancelSpec {
  void AddProcOrderId(int64_t proc_order_id) { proc_order_ids.emplace(proc_order_id); }
  auto GetProcOrderIds() const -> const std::unordered_set<int64_t>& { return proc_order_ids; }
  bool Empty() const { return proc_order_ids.empty(); }
  void Clear() { proc_order_ids.clear(); }

 private:
  std::unordered_set<int64_t> proc_order_ids;
};

}  // namespace coin2::exchange::base::order
